package org.catspaw.cherubim.lookup;

import java.lang.reflect.InvocationTargetException;
import java.util.Properties;

import org.apache.commons.discovery.DiscoveryException;
import org.apache.commons.discovery.jdk.JDKHooks;
import org.apache.commons.discovery.resource.ClassLoaders;
import org.apache.commons.discovery.tools.DefaultClassHolder;
import org.apache.commons.discovery.tools.DiscoverClass;
import org.apache.commons.discovery.tools.PropertiesHolder;
import org.apache.commons.discovery.tools.SPInterface;

public class DiscoveryLookuper implements Lookuper {

	private Properties	mappings;

	public DiscoveryLookuper(Properties mappings) {
		this.mappings = mappings;
	}

	public DiscoveryLookuper() {
		this(new Properties());
	}

	public <T> Class<? extends T> lookupImplemention(Class<T> interfaceClass)
			throws LookupException {
		ClassLoaders loaders = getClassLoaders(interfaceClass, null);
		return lookupImplemention(interfaceClass, loaders);
	}

	public <T> Class<? extends T> lookupImplemention(Class<T> interfaceClass,
			ClassLoader classLoader) throws LookupException {
		ClassLoaders loaders = getClassLoaders(interfaceClass, classLoader);
		return lookupImplemention(interfaceClass, loaders);
	}

	public <T> Class<? extends T> lookupImplemention(Class<T> interfaceClass,
			ClassLoaders classLoaders) throws LookupException {
		DefaultClassHolder<T> holder = createDefaultClassHolder(interfaceClass);
		return DiscoverClass.find(classLoaders, new SPInterface<T>(
				interfaceClass), new PropertiesHolder(mappings), holder);
	}

	public <T> T lookupInstance(Class<T> interfaceClass) throws LookupException {
		ClassLoaders loaders = getClassLoaders(interfaceClass, null);
		return lookupInstance(interfaceClass, loaders);
	}

	public <T> T lookupInstance(Class<T> interfaceClass, ClassLoader classLoader)
			throws LookupException {
		ClassLoaders loaders = getClassLoaders(interfaceClass, classLoader);
		return lookupInstance(interfaceClass, loaders);
	}

	public <T> T lookupInstance(Class<T> interfaceClass,
			ClassLoaders classLoaders) throws LookupException {
		DefaultClassHolder<T> holder = createDefaultClassHolder(interfaceClass);
		try {
			return DiscoverClass.newInstance(classLoaders, new SPInterface<T>(
					interfaceClass), new PropertiesHolder(mappings), holder);
		} catch (DiscoveryException e) {
			throw new ImplementationNotFoundException(e);
		} catch (InstantiationException e) {
			throw new CreateInstanceException(e);
		} catch (IllegalAccessException e) {
			throw new CreateInstanceException(e);
		} catch (NoSuchMethodException e) {
			throw new CreateInstanceException(e);
		} catch (InvocationTargetException e) {
			throw new CreateInstanceException(e);
		}
	}

	private ClassLoaders getClassLoaders(Class<?> interfaceClass,
			ClassLoader classLoader) {
		ClassLoaders loaders = new ClassLoaders();
		loaders.put(JDKHooks.getJDKHooks().getThreadContextClassLoader());
		if (interfaceClass != null) {
			loaders.put(interfaceClass.getClassLoader(), false);
		}
		if (classLoader != null) {
			loaders.put(classLoader, false);
		}
		loaders.put(JDKHooks.getJDKHooks().getSystemClassLoader(), false);
		return loaders;
	}

	private <T> DefaultClassHolder<T> createDefaultClassHolder(
			Class<T> interfaceClass) {
		Class<? extends T> defaultImplClass = findDefaultImplClass(interfaceClass);
		if (defaultImplClass != null) {
			return new DefaultClassHolder<T>(defaultImplClass);
		}
		String defaultImplClassName = findDefaultImplClassName(interfaceClass);
		if (defaultImplClassName != null) {
			return new DefaultClassHolder<T>(defaultImplClassName);
		}
		return null;
	}

	protected <T> Class<? extends T> findDefaultImplClass(
			Class<T> interfaceClass) {
		return null;
	}

	protected <T> String findDefaultImplClassName(Class<T> interfaceClass) {
		return null;
	}

	public Object lookupInstance(String name) throws LookupException {
		ClassLoader classLoader = getClassLoader();
		Class<?> clazz;
		try {
			clazz = Class.forName(name, true, classLoader);
		} catch (ClassNotFoundException e) {
			throw new ImplementationNotFoundException(e);
		}
		return lookupInstance(clazz, classLoader);
	}

	private ClassLoader getClassLoader() {
		ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();
		if (classLoader == null) {
			classLoader = DiscoveryLookuper.class.getClassLoader();
		}
		return classLoader;
	}

	public Properties getMappings() {
		return mappings;
	}
}
